<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 6.3.0">

  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha256-Z1K5uhUaJXA7Ll0XrZ/0JhX4lAtZFpT6jkKrEDT0drU=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"example.com","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.14.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":{"enable":false,"style":null},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":-1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>

    <meta name="description" content="socket编程     服务端和客户端初始化 socket ，得到⽂件描述符；  服务端调⽤ bind ，将绑定在 IP 地址和端⼝;  服务端调⽤ listen ，进⾏监听；  服务端调⽤ accept ，等待客户端连接；  客户端调⽤ connect ，向服务器端的地址和端⼝发起连接请求；  服务端 accept 返回⽤于传输的 socket 的⽂件描述符；  客户端调⽤ write 写⼊数">
<meta property="og:type" content="article">
<meta property="og:title" content="Socket编程 —— 使用 select、poll、epoll">
<meta property="og:url" content="http://example.com/2021/11/20/socket/index.html">
<meta property="og:site_name" content="JsyBlog">
<meta property="og:description" content="socket编程     服务端和客户端初始化 socket ，得到⽂件描述符；  服务端调⽤ bind ，将绑定在 IP 地址和端⼝;  服务端调⽤ listen ，进⾏监听；  服务端调⽤ accept ，等待客户端连接；  客户端调⽤ connect ，向服务器端的地址和端⼝发起连接请求；  服务端 accept 返回⽤于传输的 socket 的⽂件描述符；  客户端调⽤ write 写⼊数">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://example.com/2021/11/20/socket/socket-state.png">
<meta property="og:image" content="http://example.com/2021/11/20/socket/epoll.png">
<meta property="article:published_time" content="2021-11-20T12:27:00.000Z">
<meta property="article:modified_time" content="2022-03-16T19:14:33.702Z">
<meta property="article:author" content="SongyangJi">
<meta property="article:tag" content="Linux">
<meta property="article:tag" content="IO">
<meta property="article:tag" content="Socket">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://example.com/2021/11/20/socket/socket-state.png">


<link rel="canonical" href="http://example.com/2021/11/20/socket/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"http://example.com/2021/11/20/socket/","path":"2021/11/20/socket/","title":"Socket编程 —— 使用 select、poll、epoll"}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>Socket编程 —— 使用 select、poll、epoll | JsyBlog</title>
  








  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <div class="column">
      <header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <p class="site-title">JsyBlog</p>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger" aria-label="搜索" role="button">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</header>
        
  
  <aside class="sidebar">

    <div class="sidebar-inner sidebar-nav-active sidebar-toc-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
            <div class="post-toc animated"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#socket%E7%BC%96%E7%A8%8B"><span class="nav-number">1.</span> <span class="nav-text">socket编程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#select%E5%87%BD%E6%95%B0"><span class="nav-number">2.</span> <span class="nav-text">select函数</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#poll-%E5%87%BD%E6%95%B0"><span class="nav-number">3.</span> <span class="nav-text">poll 函数</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#epoll-%E5%87%BD%E6%95%B0"><span class="nav-number">4.</span> <span class="nav-text">epoll 函数</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3"><span class="nav-number">4.1.</span> <span class="nav-text">相关接口</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#epoll-create"><span class="nav-number">4.1.1.</span> <span class="nav-text">epoll_create()</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#int-epoll-ctl"><span class="nav-number">4.1.2.</span> <span class="nav-text">int epoll_ctl()</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#int-epoll-wait"><span class="nav-number">4.1.3.</span> <span class="nav-text">int epoll_wait()</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#epoll%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F"><span class="nav-number">4.2.</span> <span class="nav-text">epoll的工作模式</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1-LT%E6%A8%A1%E5%BC%8F"><span class="nav-number">4.2.1.</span> <span class="nav-text">1. LT模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-ET%E6%A8%A1%E5%BC%8F"><span class="nav-number">4.2.2.</span> <span class="nav-text">2. ET模式</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#epoll%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86"><span class="nav-number">4.3.</span> <span class="nav-text">epoll的实现原理</span></a></li></ol></li></ol></div>
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">SongyangJi</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">251</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">45</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">109</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>

        </div>
      </div>
    </div>

    
  </aside>


    </div>

    <div class="main-inner post posts-expand">


  


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/20/socket/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="Socket编程 —— 使用 select、poll、epoll | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Socket编程 —— 使用 select、poll、epoll
        </h1>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-20 20:27:00" itemprop="dateCreated datePublished" datetime="2021-11-20T20:27:00+08:00">2021-11-20</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-17 03:14:33" itemprop="dateModified" datetime="2022-03-17T03:14:33+08:00">2022-03-17</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/IO/" itemprop="url" rel="index"><span itemprop="name">IO</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
        <h2 id="socket编程"><a href="#socket编程" class="headerlink" title="socket编程"></a>socket编程</h2><img src="socket-state.png" style="zoom:50%;" />



<ol>
<li><p>服务端和客户端初始化 socket ，得到⽂件描述符；</p>
</li>
<li><p>服务端调⽤ bind ，将绑定在 IP 地址和端⼝;</p>
</li>
<li><p>服务端调⽤ listen ，进⾏监听；</p>
</li>
<li><p>服务端调⽤ accept ，等待客户端连接；</p>
</li>
<li><p>客户端调⽤ connect ，向服务器端的地址和端⼝发起连接请求；</p>
</li>
<li><p>服务端 accept 返回⽤于传输的 socket 的⽂件描述符；</p>
</li>
<li><p>客户端调⽤ write 写⼊数据；服务端调⽤ read 读取数据；</p>
</li>
<li><p>客户端断开连接时，会调⽤ close ，那么服务端 read 读取数据的时候，就会读取到了 EOF ，待处理完</p>
</li>
</ol>
<p>数据后，服务端调⽤ close ，表示连接关闭。</p>
<p>这⾥需要注意的是，服务端调⽤ accept 时，连接成功了会返回⼀个已完成连接的 socket，后续⽤来传输数据。</p>
<p>所以，监听的 socket 和真正⽤来传送数据的 socket，是两个 socket，⼀个叫作<strong>监听 socket</strong>，⼀个叫作<strong>已完成连接 socket</strong>。</p>
<p>成功连接建⽴之后，双⽅开始通过 read 和 write 函数来读写数据，就像往⼀个⽂件流⾥⾯写东⻄⼀样。</p>
<h2 id="select函数"><a href="#select函数" class="headerlink" title="select函数"></a>select函数</h2><p>select 实现多路复⽤的⽅式是，<strong>将已连接的 Socket 都放到⼀个⽂件描述符集合</strong>，</p>
<p>然后<strong>调⽤ select 函数将⽂件描述符集合拷⻉到内核⾥，让内核来检查是否有⽹络事件产⽣</strong>，检查的⽅式很粗暴，就是通过<strong>遍历⽂件描述符集合的⽅式</strong>，<strong>当检查到有事件产⽣后，将此 Socket 标记为可读或可写， 接着再把整个⽂件描述符集合拷⻉回⽤户态⾥</strong>，然后⽤户态还需要<strong>再通过遍历的⽅法找到可读或可写的 Socke</strong>t，然后再对其处</p>
<p>理。</p>
<p>所以，对<strong>于 select 这种⽅式，需要进⾏ 2 次「遍历」⽂件描述符集合</strong>，⼀次是在内核态⾥，⼀个次是在<strong>⽤户态</strong> ，⽽且还会发⽣ <strong>2</strong> 次「拷⻉」⽂件描述符集合，先从⽤户空间传⼊内核空间，由内核修改后，再传出到⽤户空间中。</p>
<p>select 使⽤固定⻓度的 BitsMap，表示⽂件描述符集合，⽽且<strong>所⽀持的⽂件描述符的个数是有限制的</strong>，在</p>
<p>Linux 系统中，由内核中的 FD_SETSIZE 限制， 默认最⼤值为 1024 ，只能监听 0~1023 的⽂件描述符。</p>
<h2 id="poll-函数"><a href="#poll-函数" class="headerlink" title="poll 函数"></a>poll 函数</h2><p><strong>poll 不再⽤位图或数组来存储所关注的⽂件描述符，取⽽代之⽤动态数组</strong>，以<strong>链表形式来组织，突破了</strong></p>
<p><strong>select 的⽂件描述符个数限制</strong>，当然还会受到系统⽂件描述符限制。</p>
<p>但是 poll 和 select 并没有太⼤的本质区别，都是使⽤「线性结构」存储进程关注的 <strong>Socket</strong> 集合，因此<strong>都需要遍历⽂件描述符集合</strong>来找到可读或可写的 <strong>Socket</strong>，时间复杂度为 **O(n)**，</p>
<p>⽽且<strong>也需要在⽤户态与内核态之间拷⻉⽂件描述符集合</strong>，这种⽅式随着并发数上来，性能的损耗会呈指数级增⻓。</p>
<h2 id="epoll-函数"><a href="#epoll-函数" class="headerlink" title="epoll 函数"></a>epoll 函数</h2><p>epoll 通过两个⽅⾯，很好解决了 select&#x2F;poll 的问题。</p>
<ul>
<li>第⼀点，epoll 在内核⾥<strong>使⽤红⿊树来跟踪进程所有待检测的⽂件描述符</strong>，把需要监控的 socket 通过</li>
</ul>
<p><code>epoll_ctl()</code> 函数加⼊内核中的红⿊树⾥，红⿊树是个⾼效的数据结构，增删查⼀般时间复杂度是</p>
<p>O(logn) ，通过对这棵⿊红树进⾏操作，<strong>这样就不需要像 select&#x2F;poll 每次操作时都传⼊整个 socket 集合，只需要传⼊⼀个待检测的 socket，减少了内核和⽤户空间⼤量的数据拷⻉和内存分配</strong>。</p>
<ul>
<li>第⼆点， epoll 使⽤<strong>事件驱动</strong>的机制，<strong>内核⾥维护了⼀个链表来记录就绪事件</strong>，<strong>当某个 socket 有事件发⽣时，通过回调函数内核会将其加⼊到这个就绪事件列表中</strong>，当⽤户调⽤ <code>epoll_wait() </code>函数时，<strong>只会返回有事件发⽣的⽂件描述符的个数，不需要像 select&#x2F;poll 那样轮询扫描整个 socket 集合</strong>，⼤⼤提⾼了检测的效率。<br><img src="/2021/11/20/socket/epoll.png"></li>
</ul>
<h3 id="相关接口"><a href="#相关接口" class="headerlink" title="相关接口"></a>相关接口</h3><p>epoll API是Linux专有的特性，相较于<code>select</code>和<code>poll</code>，<code>epoll</code>更加灵活且<strong>没有描述符限制</strong>。<code>epoll</code>设计也与<code>select</code>和<code>poll</code>不同，主要包含以下三个接口：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_create</span><span class="params">(<span class="type">int</span> size)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">epoll_ctl</span><span class="params">(<span class="type">int</span> epfd, <span class="type">int</span> op, <span class="type">int</span> fd, <span class="keyword">struct</span> epoll_event *event)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">epoll_wait</span><span class="params">(<span class="type">int</span> epfd, <span class="keyword">struct</span> epoll_event * events, <span class="type">int</span> maxevents, <span class="type">int</span> timeout)</span>;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>下面依次介绍。</p>
</blockquote>
<h4 id="epoll-create"><a href="#epoll-create" class="headerlink" title="epoll_create()"></a>epoll_create()</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_create</span><span class="params">(<span class="type">int</span> size)</span>；<span class="comment">//创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大</span></span><br></pre></td></tr></table></figure>

<p>创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大，这个参数不同于select()中的第一个参数，给出最大监听的fd+1的值，<em>参数size并不是限制了epoll所能监听的描述符最大个数，只是对内核初始分配内部数据结构的一个建议</em>。<br>当创建好epoll句柄后，它就会占用一个fd值，在linux下如果查看&#x2F;proc&#x2F;进程id&#x2F;fd&#x2F;，是能够看到这个fd的，所以在使用完epoll后，必须调用close()关闭，否则可能导致fd被耗尽。</p>
<h4 id="int-epoll-ctl"><a href="#int-epoll-ctl" class="headerlink" title="int epoll_ctl()"></a>int epoll_ctl()</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_ctl</span><span class="params">(<span class="type">int</span> epfd, <span class="type">int</span> op, <span class="type">int</span> fd, <span class="keyword">struct</span> epoll_event *ev)</span>; <span class="comment">//修改兴趣列表（事件注册函数）</span></span><br></pre></td></tr></table></figure>

<p>该函数是对指定描述符fd执行op操作。</p>
<ul>
<li>epfd：是epoll_create()的返回值。</li>
<li>op：表示op操作，用三个宏来表示：1. <code>EPOLL_CTL_ADD</code>注册新的fd到epfd中；2. <code>EPOLL_CTL_MOD</code>修改已经注册的fd的监听事件；3. <code>EPOLL_CTL_DEL</code>从<code>epfd</code>中删除一个fd。</li>
<li>fd：是需要监听的fd（文件描述符）</li>
<li>epoll_event：是告诉内核需要监听什么事，struct epoll_event结构如下：</li>
</ul>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> &#123;</span></span><br><span class="line">  <span class="type">__uint32_t</span> events;  <span class="comment">/* Epoll events */</span></span><br><span class="line">  <span class="type">epoll_data_t</span> data;  <span class="comment">/* User data variable */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><strong>events可以是以下几个宏的集合</strong>：</p>
<ol>
<li>EPOLLIN ：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）；</li>
<li>EPOLLOUT：表示对应的文件描述符可以写；</li>
<li>EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；</li>
<li>EPOLLERR：表示对应的文件描述符发生错误；</li>
<li>EPOLLHUP：表示对应的文件描述符被挂断；</li>
<li>EPOLLET： bsEPOLL设为边缘触发（Edge Triggered)模式，这是相对于水平触发(Level Triggered）来说的。</li>
<li>EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里。</li>
</ol>
<p>其中<code>data</code>的类型为：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">union</span> <span class="title">epoll_data</span> &#123;</span></span><br><span class="line">  <span class="type">void</span>    *ptr; <span class="comment">//pointer to user defined data</span></span><br><span class="line">  <span class="type">int</span>     fd; <span class="comment">//file descriptor</span></span><br><span class="line">  uint_32 u32; <span class="comment">//32-bit integer</span></span><br><span class="line">  uint_64 u64; <span class="comment">//64-bit integer</span></span><br><span class="line">&#125; <span class="type">epoll_data_t</span>;</span><br></pre></td></tr></table></figure>

<p><strong><code>data</code>字段是唯一可以获知同这个事件相关的文件描述符的途径</strong>，因此调用<code>epoll_ctl()</code>将文件描述符添加到兴趣列表中时，应该要么将<code>ev.data.fd</code>设为文件描述符，要么将<code>ev.data.ptr</code>设为指向包含该文件描述的结构体。</p>
<h4 id="int-epoll-wait"><a href="#int-epoll-wait" class="headerlink" title="int epoll_wait()"></a>int epoll_wait()</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_wait</span><span class="params">(<span class="type">int</span> epfd, <span class="keyword">struct</span> epoll_event *evlist, <span class="type">int</span> maxevents, <span class="type">int</span> timeout)</span>;</span><br></pre></td></tr></table></figure>

<p>数组<code>evlist</code>的空间由调用者负责申请;</p>
<p>等待epfd上的io事件，最多返回maxevents个事件。<br>参数evlist用来从内核得到事件的集合，maxevents告之内核这个evlist有多大，这个maxevents的值不能大于创建epoll_create()时的size，参数timeout是超时时间（毫秒，0会立即返回，-1将不确定，也有说法说是永久阻塞）。<strong>该函数返回需要处理的事件数目，如返回0表示已超时</strong>。</p>
<h3 id="epoll的工作模式"><a href="#epoll的工作模式" class="headerlink" title="epoll的工作模式"></a>epoll的工作模式</h3><p>epoll对文件描述符的操作有两种模式：<strong>LT（level trigger）</strong>和<strong>ET（edge trigger）</strong>。LT模式是默认模式，LT模式与ET模式的区别如下：</p>
<p><strong>LT模式</strong>：当epoll_wait检测到描述符事件发生并将此事件通知应用程序，<strong>应用程序可以不立即处理该事件</strong>。下次调用epoll_wait时，会再次响应应用程序并通知此事件。</p>
<p><strong>ET模式</strong>：当epoll_wait检测到描述符事件发生并将此事件通知应用程序，<strong>应用程序必须立即处理该事件</strong>。如果不处理，下次调用epoll_wait时，不会再次响应应用程序并通知此事件。</p>
<p>LT模式下，<strong>主要缓冲区数据一次没有处理完，那么下次<code>epoll_wait</code>返回时，还会返回这个句柄</strong>；</p>
<p>而ET模式下，缓冲区数据一次没处理结束，那么下次就不会再通知了，只在第一次返回．所以在ET模式下，一般是通过while循环，一次性读完全部数据<strong>．epoll默认使用的是LT</strong>．</p>
<h4 id="1-LT模式"><a href="#1-LT模式" class="headerlink" title="1. LT模式"></a>1. LT模式</h4><p>LT(level triggered)是默认的工作方式，并且<strong>同时支持block和no-block</strong> socket.在这种做法中，内核告诉你一个文件描述符是否就绪了，然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作，内核还是会继续通知你的。</p>
<h4 id="2-ET模式"><a href="#2-ET模式" class="headerlink" title="2. ET模式"></a>2. ET模式</h4><p><strong>ET(edge-triggered)是高速工作方式</strong>，只支持no-block socket。在这种模式下，当描述符从未就绪变为就绪时，内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪，并且不会再为那个文件描述符发送更多的就绪通知，直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如，你在发送，接收或者接收请求，或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误）。但是请注意，如果一直不对这个fd作IO操作(从而导致它再次变成未就绪)，内核不会发送更多的通知(only once)</p>
<p><strong>ET模式在很大程度上减少了epoll事件被重复触发的次数</strong>，因此效率要比LT模式高。epoll工作在ET模式的时候，<strong>必须使用非阻塞套接口</strong>，以避免由于一个文件句柄的阻塞读&#x2F;阻塞写操作把处理多个文件描述符的任务饿死。</p>
<p><strong>假如有这样一个例子：</strong></p>
<ol>
<li>我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符</li>
<li>这个时候从管道的另一端被写入了2KB的数据</li>
<li>调用epoll_wait(2)，并且它会返回RFD，说明它已经准备好读取操作</li>
<li>然后我们读取了1KB的数据</li>
<li>调用epoll_wait(2)（两种工作模式有不同的差别）</li>
</ol>
<p><strong>LT模式：</strong><br>如果是LT模式，那么在第5步调用epoll_wait(2)之后，仍然能受到通知。</p>
<p><strong>ET模式：</strong><br>只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候，调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。</p>
<h3 id="epoll的实现原理"><a href="#epoll的实现原理" class="headerlink" title="epoll的实现原理"></a>epoll的实现原理</h3><p>在linux，一切皆文件．所以当调用<code>epoll_create</code>时，内核给这个<code>epoll</code>分配一个文件描述符，但是这个不是普通的文件，而是只服务于epoll。</p>
<p>当内核初始化epoll时，会开辟一块内核高速缓冲区，用于放置我们监听的对端socket，这些socket会以<strong>红黑树节点的形式</strong>保存在内核的<code>cache</code>里，以支持快速的查找，插入，删除。</p>
<p>同时，建立了一个<strong>list链表，用于存储准备就绪的事件</strong>．所以调用<code>epoll_wait</code>时，在timeout时间内，只是简单的观察这个list链表是否有数据，如果没有，则睡眠至超时时间到返回；如果有数据，则在超时时间到，拷贝至用户态<code>events</code>数组中．</p>
<p><em>那么，这个准备就绪list链表是怎么维护的呢？</em><br>当我们执行<code>epoll_ctl()</code>时，除了把socket_fd放到epoll系统里对应的红黑树上之外，还会<strong>给内核中断处理程序注册一个回调函数</strong>，告诉内核，<strong>如果这个句柄的中断到了，就把它放到准备就绪list链表里</strong>。所以，当一个socket上有数据到了，内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。</p>
<p>epoll主要由两个结构体：eventpoll与epitem。epitem是每一个IO所对应的的事件。比如 <code>epoll_ctl()</code>的<code>EPOLL_CTL_ADD</code>操作的时候，就需要创建一个<code>epitem</code>。<code>eventpoll</code>是每一个epoll所对应的。比如<code>epoll_create</code>就是创建一个<code>eventpoll</code>。如下图所示，<code>eventpoll</code> 包含了 <code>lock</code>、<code>mtx</code>、<code>wq（等待队列）</code>与 <code>rdlist</code> 等成员，其中 <code>rdlist</code> 和 <code>rbr</code> 是我们所关心的。</p>

    </div>

    
    
    

    <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/Linux/" rel="tag"># Linux</a>
              <a href="/tags/IO/" rel="tag"># IO</a>
              <a href="/tags/Socket/" rel="tag"># Socket</a>
          </div>

        

          <div class="post-nav">
            <div class="post-nav-item">
                <a href="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/" rel="prev" title="Unix/Linux 中的五种 IO 模型">
                  <i class="fa fa-chevron-left"></i> Unix/Linux 中的五种 IO 模型
                </a>
            </div>
            <div class="post-nav-item">
                <a href="/2021/11/20/Linux%E8%AF%BB%E5%86%99%E6%96%87%E4%BB%B6%E7%9B%B8%E5%85%B3%E5%BA%93%E5%87%BD%E6%95%B0/" rel="next" title="Linux读写文件相关库函数">
                  Linux读写文件相关库函数 <i class="fa fa-chevron-right"></i>
                </a>
            </div>
          </div>
    </footer>
  </article>
</div>






</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">SongyangJi</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>
  <div class="sidebar-dimmer"></div>
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up fa-lg"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/schemes/muse.js"></script><script src="/js/next-boot.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/hexo-generator-searchdb/1.4.1/search.js" integrity="sha256-1kfA5uHPf65M5cphT2dvymhkuyHPQp5A53EGZOnOLmc=" crossorigin="anonymous"></script>
<script src="/js/third-party/search/local-search.js"></script>





  





</body>
</html>
